/*
 * TeleStax, Open Source Cloud Communications  Copyright 2012. 
 * and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.mobicents.protocols.ss7.isup.impl.stack.timers;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.mobicents.protocols.ss7.isup.ISUPEvent;
import org.mobicents.protocols.ss7.isup.ISUPListener;
import org.mobicents.protocols.ss7.isup.ISUPProvider;
import org.mobicents.protocols.ss7.isup.ISUPStack;
import org.mobicents.protocols.ss7.isup.ISUPTimeoutEvent;
import org.mobicents.protocols.ss7.isup.ParameterException;
import org.mobicents.protocols.ss7.isup.impl.CircuitManagerImpl;
import org.mobicents.protocols.ss7.isup.impl.ISUPStackImpl;
import org.mobicents.protocols.ss7.isup.impl.message.AbstractISUPMessage;
import org.mobicents.protocols.ss7.isup.message.ISUPMessage;
import org.mobicents.protocols.ss7.mtp.Mtp3;
import org.mobicents.protocols.ss7.mtp.Mtp3TransferPrimitive;
import org.mobicents.protocols.ss7.mtp.Mtp3TransferPrimitiveFactory;
import org.mobicents.protocols.ss7.mtp.Mtp3UserPartBaseImpl;
import org.mobicents.protocols.ss7.scheduler.Clock;
import org.mobicents.protocols.ss7.scheduler.DefaultClock;
import org.mobicents.protocols.ss7.scheduler.Scheduler;
/**
 * @author baranowb
 * 
 */
public abstract class EventTestHarness /*extends TestCase*/ implements ISUPListener {

	protected ISUPStack stack;
	protected ISUPProvider provider;

	protected Clock clock;
	protected Scheduler scheduler;
	
	protected TimerTestMtp3UserPart userPart;

	// events received by by this listener
	protected List<EventReceived> localEventsReceived;
	// events sent to remote ISUP peer.
	protected List<EventReceived> remoteEventsReceived;
	
	protected static final int dpc=1;
	protected static final int localSpc=2;
	protected static final int ni=2;
	
	public void setUp() throws Exception {
		clock = new DefaultClock();
		scheduler = new Scheduler();
        scheduler.setClock(clock);
        scheduler.start();
        
		this.userPart = new TimerTestMtp3UserPart();
		this.userPart.start();
		this.stack = new ISUPStackImpl(scheduler,localSpc,ni);		
		this.provider = this.stack.getIsupProvider();
		this.provider.addListener(this);
		this.stack.setMtp3UserPart(this.userPart);
		CircuitManagerImpl cm = new CircuitManagerImpl();
		cm.addCircuit(1, dpc);
		this.stack.setCircuitManager(cm);
		this.stack.start();
		localEventsReceived = new ArrayList<EventTestHarness.EventReceived>();
		remoteEventsReceived = new ArrayList<EventTestHarness.EventReceived>();		
	}

	
	public void tearDown() throws Exception {
		//this is done in tests
		//this.stack.stop();
	}


	protected void compareEvents(List<EventReceived> expectedLocalEvents, List<EventReceived> expectedRemoteEventsReceived) {
		
		if (expectedLocalEvents.size() != this.localEventsReceived.size()) {
			fail("Size of local events: " + this.localEventsReceived.size() + ", does not equal expected events: " + expectedLocalEvents.size()+"\n"+
					doStringCompare(localEventsReceived, expectedLocalEvents));
		}

		if (expectedRemoteEventsReceived.size() != this.remoteEventsReceived.size()) {
			fail("Size of remote events: " + this.remoteEventsReceived.size() + ", does not equal expected events: " + expectedRemoteEventsReceived.size()+"\n"+
					doStringCompare(remoteEventsReceived, expectedRemoteEventsReceived));
		}
		
		for (int index = 0; index < expectedLocalEvents.size(); index++) {			
			assertEquals(localEventsReceived.get(index),expectedLocalEvents.get(index), "Local received event does not match, index[" + index + "]");
		}

		for (int index = 0; index < expectedLocalEvents.size(); index++) {
			assertEquals( remoteEventsReceived.get(index),expectedRemoteEventsReceived.get(index), "Remote received event does not match, index[" + index + "]");
		}
	}

	protected String doStringCompare(List lst1,List lst2)
	{
		StringBuilder sb = new StringBuilder();
		int size1  = lst1.size();
		int size2  = lst2.size();
		int count = size1;
		if(count<size2)
		{
			count = size2;
		}
		
		for(int index = 0;count>index;index++)
		{
			String s1 = size1>index ? lst1.get(index).toString() : "NOP";
			String s2 = size2>index ? lst2.get(index).toString() : "NOP";
			sb.append(s1).append(" - ").append(s2).append("\n");
		}
		return sb.toString();
	}
	
	
	public void onEvent(ISUPEvent event) {
		this.localEventsReceived.add(new MessageEventReceived(System.currentTimeMillis(), event));

	}

	public void onTimeout(ISUPTimeoutEvent event) {
		this.localEventsReceived.add(new TimeoutEventReceived(System.currentTimeMillis(), event));

	}

	// method implemented by test, to answer stack.
	protected void doAnswer() {
		ISUPMessage answer = getAnswer();
		int opc = 1;
		int dpc = 2;
		int si = Mtp3._SI_SERVICE_ISUP;
		int ni = 2;
		int sls = 3;
//		int ssi = ni << 2;

//		ByteArrayOutputStream bout = new ByteArrayOutputStream();
//		// encoding routing label
//		bout.write((byte) (((ssi & 0x0F) << 4) | (si & 0x0F)));
//		bout.write((byte) dpc);
//		bout.write((byte) (((dpc >> 8) & 0x3F) | ((opc & 0x03) << 6)));
//		bout.write((byte) (opc >> 2));
//		bout.write((byte) (((opc >> 10) & 0x0F) | ((sls & 0x0F) << 4)));

		try {
			byte[] message = ((AbstractISUPMessage) answer).encode();
//			bout.write(message);
//			byte[] msg = bout.toByteArray();

//			this.userPart.toRead.add(msg);
			Mtp3TransferPrimitiveFactory factory = stack.getMtp3UserPart().getMtp3TransferPrimitiveFactory();
			Mtp3TransferPrimitive mtpMsg = factory.createMtp3TransferPrimitive(si, ni, 0, opc, dpc, sls, message);
			this.userPart.sendTransferMessageToLocalUser(mtpMsg, mtpMsg.getSls());
			
		} catch (Exception e) {

			e.printStackTrace();
			fail("Failed on receive message: " + e);
		}
	}

	protected void doWait(long t) throws InterruptedException {
		Thread.currentThread().sleep(t);
	}

	/**
	 * return orignial request
	 * @return
	 */
	protected abstract ISUPMessage getRequest();
	
	
	/**
	 * return answer to be sent.
	 * @return
	 */
	protected abstract ISUPMessage getAnswer();

	
	
	protected class EventReceived {
		private long tstamp;

		/**
		 * @param tstamp
		 */
		public EventReceived(long tstamp) {
			super();
			this.tstamp = tstamp;
		}

		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			EventReceived other = (EventReceived) obj;

			if (tstamp != other.tstamp) {
				// we have some tolerance
				if (other.tstamp - 100 < tstamp || other.tstamp + 100 > tstamp) {
					return true;
				} else {
					return false;
				}
			}
			return true;
		}
	}

	protected class MessageEventReceived extends EventReceived {
		private ISUPEvent event;

		/**
		 * @param tstamp
		 */
		public MessageEventReceived(long tstamp, ISUPEvent event) {
			super(tstamp);
			this.event = event;
		}

		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (!super.equals(obj))
				return false;
			if (getClass() != obj.getClass())
				return false;
			MessageEventReceived other = (MessageEventReceived) obj;
			if (event == null) {
				if (other.event != null)
					return false;
				// FIXME: use event equal?
			} else if (event.getMessage().getMessageType().getCode() != other.event.getMessage().getMessageType().getCode())
				return false;
			return true;
		}

		public String toString() {
			return "MessageEventReceived [event=" + event + ", tstamp= " + super.tstamp + "]";
		}
	}

	protected class TimeoutEventReceived extends EventReceived {
		private ISUPTimeoutEvent event;

		public TimeoutEventReceived(long tstamp, ISUPTimeoutEvent event) {
			super(tstamp);
			this.event = event;
			
		}

		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (!super.equals(obj))
				return false;
			if (getClass() != obj.getClass())
				return false;
			TimeoutEventReceived other = (TimeoutEventReceived) obj;
			if (event == null) {
				if (other.event != null)
					return false;
				// FIXME: use event equal?
			} else if (event.getMessage().getMessageType().getCode() != other.event.getMessage().getMessageType().getCode()) {
				return false;
			} else if (event.getTimerId() != other.event.getTimerId()) {
				return false;
			}

			return true;
		}

		public String toString() {
			return "TimeoutEventReceived [event=" + event + ", tstamp= " + super.tstamp + "]";
		}

	}

	
	private class TimerTestMtp3UserPart extends Mtp3UserPartBaseImpl
	{

		public void sendTransferMessageToLocalUser(Mtp3TransferPrimitive msg, int seqControl) {
			super.sendTransferMessageToLocalUser(msg, seqControl);
		}

		@Override
		public void sendMessage(Mtp3TransferPrimitive mtpMsg) throws IOException {

			// here we have to parse ISUPMsg and store in receivedRemote
			long tstamp = System.currentTimeMillis();
			byte[] payload = mtpMsg.getData();
			int commandCode = payload[2];
			AbstractISUPMessage msg = (AbstractISUPMessage) provider.getMessageFactory().createCommand(commandCode);
			try {
				msg.decode(payload, provider.getParameterFactory());
				MessageEventReceived event = new MessageEventReceived(tstamp, new ISUPEvent(provider, msg , dpc));
				remoteEventsReceived.add(event);
			} catch (ParameterException e) {
				e.printStackTrace();
				fail("Failed on message write: " + e);
			}
		}

		
//		private ArrayList<byte[]> toRead = new ArrayList();
////		/* (non-Javadoc)
////		 * @see org.mobicents.protocols.ss7.mtp.Mtp3UserPart#execute()
////		 */
////		@Override
////		public void execute() throws IOException {
////			
////			
////		}
//
//		/* (non-Javadoc)
//		 * @see org.mobicents.protocols.ss7.mtp.Mtp3UserPart#read(java.nio.ByteBuffer)
//		 */
//		@Override
//		public int read(ByteBuffer arg0) throws IOException {
//			if(toRead.size()>0)
//			{
//				byte[] data = toRead.remove(0);
//				arg0.put(data);
//				return data.length;
//			}
//			return 0;
//		}
//
//		/* (non-Javadoc)
//		 * @see org.mobicents.protocols.ss7.mtp.Mtp3UserPart#write(java.nio.ByteBuffer)
//		 */
//		@Override
//		public int write(ByteBuffer arg0) throws IOException {
//			// here we have to parse ISUPMsg and store in receivedRemote
//			long tstamp = System.currentTimeMillis();
//			byte[] msu = new byte[arg0.limit()];
//			arg0.get(msu);
//			//arg0.s
//			// FIXME: change this, dont copy over and over.
//			int commandCode = msu[7];// 3(RL) + 1(SI)+2(CIC) -
//			// http://pt.com/page/tutorials/ss7-tutorial/mtp
//			byte[] payload = new byte[msu.length - 5];
//			System.arraycopy(msu, 5, payload, 0, payload.length);
//			// for post processing
//			AbstractISUPMessage msg = (AbstractISUPMessage) provider.getMessageFactory().createCommand(commandCode);
//			try {
//				msg.decode(payload, provider.getParameterFactory());
//				MessageEventReceived event = new MessageEventReceived(tstamp, new ISUPEvent(provider, msg));
//				remoteEventsReceived.add(event);
//				return msu.length;
//			} catch (ParameterException e) {
//				e.printStackTrace();
//				fail("Failed on message write: " + e);
//			}
//			return 0;
//		}
//	
//		public void print(StringBuffer sb, int leftPad, int descPad) {
//			 // left pad
//	        FormatterHelp.createPad(sb, leftPad);
//
//	        // Add name
//	        sb.append(this.linksetName);
//
//	        // check if length is less than Link.NAME_SIZE, add padding
//	        if (this.linksetName.length() < Linkset.NAME_SIZE) {
//	            FormatterHelp.createPad(sb, Linkset.NAME_SIZE - this.linksetName.length());
//	        }
//
//	        // add desc padding
//	        FormatterHelp.createPad(sb, descPad);
//
//	        // type is dahdi
//	        sb.append("dahdi");
//
//	        // add desc padding
//	        FormatterHelp.createPad(sb, descPad);
//
//	        // add opc
//	        sb.append(LINKSET_OPC).append(FormatterHelp.EQUAL_SIGN).append(this.opc);
//
//	        // opc can be max 8 (ANSI is max 24bits) digits. Add padding if its not
//	        int length = (Integer.toString(this.opc).length());
//	        if (length < 8) {
//	            FormatterHelp.createPad(sb, 8 - length);
//	        }
//
//	        // add desc padding
//	        FormatterHelp.createPad(sb, descPad);
//
//	        // add apc
//	        sb.append(LINKSET_APC).append(FormatterHelp.EQUAL_SIGN).append(this.apc);
//
//	        // opc can be max 8 (ANSI is max 24bits) digits. Add padding if its not
//	        length = (Integer.toString(this.apc).length());
//	        if (length < 8) {
//	            FormatterHelp.createPad(sb, 8 - length);
//	        }
//
//	        // add desc padding
//	        FormatterHelp.createPad(sb, descPad);
//
//	        // add NI
//	        sb.append(LINKSET_NI).append(FormatterHelp.EQUAL_SIGN).append(this.ni);
//
//	        // add desc padding
//	        FormatterHelp.createPad(sb, descPad);
//
//	        // add state
//	        sb.append(LINKSET_STATE).append(FormatterHelp.EQUAL_SIGN).append(FormatterHelp.getLinksetState(this.state));
//
//	        sb.append(FormatterHelp.NEW_LINE);
//
//	        for (FastMap.Entry<String, Link> e = this.links.head(), end = this.links.tail(); (e = e.getNext()) != end;) {
//	            Link link = e.getValue();
//	            link.print(sb, 4, 2);
//	            sb.append(FormatterHelp.NEW_LINE);
//	        }
//
//		}
		
	}

}
